#pragma once

#include<iostream>
#include<vector>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include"Task.hpp"



const int _default_channel_num = 5;

class Channel{
public:
    Channel(int wfd, pid_t subid)
        :_wfd(wfd), _subid(subid)
    {
        _name = "Channel_" + to_string(_wfd) + "_" + to_string(_subid) + "@" + to_string(_load_times);
    }

    ~Channel(){}

    int getwfd() const{
        return _wfd;
    }

    pid_t getsubid() const{
        return _subid;
    }

    int getload() const{
        return _load_times;
    }

    string getname() const{
        return _name;
    }

    void Send(int taskcode){
        //将信息码发送
        int Code = taskcode;
        int wd = write(_wfd, &Code, sizeof(Code));
        if(wd == -1){
            cout << "write ERROR!" << endl;
            exit(errno);
        }
    }

    void Load_times_addone(){
        ++_load_times;
        //因为名字是有带负载次数的，所以需要修改一下名字
        AdjustName();
    }



    void CloseWd(){
        int closed = close(_wfd);
        if(closed == -1){
            cout << "close ERROR!" << endl;
            exit(errno);
        }
    }

    void WaitPid(){
        int Status;
        int rd = waitpid(_subid, &Status, 0);
        if(rd == _subid){
            cout << "子进程" << _subid << "回收成功" << endl;
        }
        else{
            cout << "waitpid ERROR!" << endl;
            exit(errno);
        }
    }
private:
    int _wfd;// 管道写端的文件描述符
    pid_t _subid;// 子进程pid
    int _load_times;// 负载次数 -> 选择管道时，选择管道负载最小的
    string _name;// 给管道命名，用于debug测试

    void AdjustName(){
        _name = "Channel_" + to_string(_wfd) + "_" + to_string(_subid) + "@" + to_string(_load_times);
    }
};

struct Channel_LoadCmp{
    bool operator()(const Channel& c1, const Channel& c2){
        return (c1.getload() < c2.getload());
    }
};



////////////////////////////////////////////////////////////////////////////////////////////////////////
class Process_Administrator{
public:
    void PushChannel(int wfd, int subid){
        _Channel_vector.emplace_back(wfd, subid);
    }

    void Print_Channel_name(){
        for(auto& channel : _Channel_vector){
            cout << channel.getname() << endl;
        }
    }

    Channel& Select_Channel(){
        auto it = min_element(_Channel_vector.begin(), _Channel_vector.end(), Channel_LoadCmp());
        unordered_map<int, int> um;
        int i = 0, j = 0;
        while(j < _Channel_vector.size()){
            if(it->getload() == _Channel_vector[j].getload())
                um.emplace(i++, j);
            ++j;
        }
        // 选择负载最低的管道，如果多个负载率相同，那就随机选取一个
        // um中有i个元素 Key为映射坐标(用来选取随机数) value指向管道(vector中管道的位置)
        int choicei = rand() % i; // choicei 0 ~ i-1
        int pos = um[choicei];
        return _Channel_vector[pos];
    }

    void Process_Stop(){
        for(int i = 0; i < _Channel_vector.size(); ++i){
            _Channel_vector[i].CloseWd();
        }
    }

    void Process_Wait(){
        for(int i = 0; i < _Channel_vector.size(); ++i){
            _Channel_vector[i].WaitPid();
        }
    }

    // 不处理创建管道代码会出点小bug
    void Process_Stop_Wait_Merge(){
        for(int i = 0; i < _Channel_vector.size(); ++i){
            _Channel_vector[i].CloseWd();
            _Channel_vector[i].WaitPid();
        }
    }
private:
    vector<Channel> _Channel_vector;
};



////////////////////////////////////////////////////////////////////////////////////////////////////////
class ProcessPool{
public:
    ProcessPool()
        :_ProcNum(_default_channel_num)
    {
        _task_Admin.Register(0, Upload);
        _task_Admin.Register(1, Download);
        _task_Admin.Register(2, Scan);
        _task_Admin.Register(3, Copy);
        _task_Admin.Register(4, Update);
        _task_Admin.Register(5, Log);
    }
    ~ProcessPool(){}

    void Work(int rfd){
        while(true){
            int Code = 0;
            int rd = read(rfd, &Code, sizeof(Code));
            if(rd == -1){
                cout << "read ERROR!" << endl;
                exit(errno);
            }
            else if(rd == 0){
                cout << "Process" << getpid() << " is over, Going to exit!" << endl;
                break;
            }
            else if(rd != sizeof(Code)){
                cout << "Num of Bytes which read is ERROR, it should be " << sizeof(Code) << endl;
                exit(23);
            }
            else{
                // 拿到了正常的任务码
                // 根据任务码来执行任务
                _task_Admin.Execute_task(Code);
            }
            sleep(1);
        }
    }

    void Debug(){
        _Proc_Admin.Print_Channel_name();
    }

    bool InitProcessPool(){
        for(int i = 0; i < _ProcNum; ++i){
            int pipefd[2] = {0};
            int PIPE_id = pipe(pipefd);
            if(PIPE_id == -1){
                cout << "Pipe Create ERROR!" << endl;
                return false;
            }

            pid_t subid = fork();
            if(subid < 0){
                cout << "fork ERROR!" << endl;
                return false;
            }
            else if(subid == 0){
                // child Process
                close(pipefd[1]);
                //把历史上所有的写端(兄弟进程的)全关掉 -> 博客上讲解原理
                _Proc_Admin.Process_Stop();
                Work(pipefd[0]);

                close(pipefd[0]);
                exit(0);
            }
            else{
                // father Process
                close(pipefd[0]);
                _Proc_Admin.PushChannel(pipefd[1], subid);
            }
        }
        return true;
    }

    void Run(){
        //1. 选择一条管道
        Channel& channel = _Proc_Admin.Select_Channel();
        channel.Load_times_addone();// 增加这个管道的负载次数
        cout << "Choose the Channel of " << channel.getname() << endl;
        //2.需要向指定的管道发送任务码！ 但是，在这之前，需要获取任务码。
        int taskcode = _task_Admin.Get_taskcode();
        //3.发送到管道里面
        channel.Send(taskcode);

        //4.子进程从管道读取 -> Work()函数
    }

    // 第一种方法
    void Process_StopandWait_1(){
        //终止子进程
        _Proc_Admin.Process_Stop();
        //回收子进程
        _Proc_Admin.Process_Wait();
    }

    // 第二种方法
    void Process_StopandWait_2(){
        // 第一种方法是一次性把所有的写端文件描述符都关了，然后全部一把回收
        // 但是，如果这里每次关一个，回收一个，其实会出问题 -> 需要在创建管道的时候进行一些处理！
        _Proc_Admin.Process_Stop_Wait_Merge();
        //当然，如果倒着关是可以 -> 这里就不写了 博客上讲解原理即可
        
        //下面需要去修改一下创建管道的代码
    }
private:
    Process_Administrator _Proc_Admin;
    int _ProcNum;
    Task_List _task_Admin;
};








